package zy.wq.config;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;

import zy.wq.common.InvocationResult;


public class RestAuthenticationFilter extends
	AbstractAuthenticationProcessingFilter implements InitializingBean {
	
	private static Log LOG = LogFactory.getLog(RestAuthenticationFilter.class);
	
	private HttpOutputMessageWriter httpOutputMessageWriter;
	
	private String usernameParameter = "username";
	private String passwordParameter = "password";
	private String passwordTypeParameter = "passwordtype";
	
	private boolean postOnly = true;
	
	private HttpMessageConverter jsonConvertor;
	
	public RestAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login", "POST"));
	}
	
	

	public boolean isPostOnly() {
		return postOnly;
	}



	public void setPostOnly(boolean postOnly) {
		this.postOnly = postOnly;
	}



	public String getUsernameParameter() {
		return usernameParameter;
	}

	public void setUsernameParameter(String usernameParameter) {
		this.usernameParameter = usernameParameter;
	}

	public String getPasswordParameter() {
		return passwordParameter;
	}

	public void setPasswordParameter(String passwordParameter) {
		this.passwordParameter = passwordParameter;
	}

	public String getPasswordTypeParameter() {
		return passwordTypeParameter;
	}

	public void setPasswordTypeParameter(String passwordTypeParameter) {
		this.passwordTypeParameter = passwordTypeParameter;
	}

	public HttpMessageConverter<?> getJsonConvertor() {
		return jsonConvertor;
	}

	public void setJsonConvertor(HttpMessageConverter<?> jsonConvertor) {
		this.jsonConvertor = jsonConvertor;
	}

	@Override
	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		if(jsonConvertor == null) {
			jsonConvertor = new MappingJackson2HttpMessageConverter();
		}
		if(httpOutputMessageWriter == null) {
			httpOutputMessageWriter = new HttpOutputMessageWriter();
			List<HttpMessageConverter<?>> convertors = new ArrayList<HttpMessageConverter<?>>();
			convertors.add(jsonConvertor);
			httpOutputMessageWriter.setMessageConverters(new HttpMessageConverters(convertors));
		}
		
		RestAuthenticationResultHandler resultHandler = new RestAuthenticationResultHandler(); 
		this.setAuthenticationSuccessHandler(resultHandler);
		this.setAuthenticationFailureHandler(resultHandler);
		
	}

	@Override //1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}
		UsernamePasswordAuthenticationToken authenticationResult = null;
		try{
			authenticationResult = parseAuthenticationRequest(request);//2
		}catch(Exception e){
			LOG.error("",e);
		}
		if(authenticationResult == null) {
			return null;
		}
		setDetails(request, authenticationResult);

		return this.getAuthenticationManager().authenticate(authenticationResult);
	}
	
	protected UsernamePasswordAuthenticationToken parseAuthenticationRequest(HttpServletRequest request) throws IOException {
		boolean jsonRequest = false;
		String contentTypeStr  =  request.getContentType();
		if(StringUtils.hasText(contentTypeStr)) {
			MediaType requestMediaType = MediaType.parseMediaType(contentTypeStr);
			if(requestMediaType != null && MediaType.APPLICATION_JSON.isCompatibleWith(requestMediaType)) {
				jsonRequest = true;
			}
		}
		String username = null;
		String password = null;
		String passwordType = null;
		if(jsonRequest) {
			HttpInputMessage httpMessage = new ServletServerHttpRequest(request);
			Map params = (Map)jsonConvertor.read(Map.class, httpMessage);
			username = (String)params.get(usernameParameter);
			password = (String)params.get(passwordParameter);
			passwordType = (String)params.get(passwordTypeParameter);
		}else {
			username = request.getParameter(usernameParameter);
			password = request.getParameter(passwordParameter);
			passwordType = request.getParameter(passwordTypeParameter);
		}
		if(!StringUtils.hasText(username)) {
			return null;
		}
		username = username.trim();
		Object credentials = password;
		if(StringUtils.hasText(passwordType)) {
			try{
				credentials = new PasswordAndType(password, PasswordAndType.Type.valueOf(passwordType));
			}catch(Exception e){
				LOG.error("Invalid Password Type: "+passwordType);
			}
		}
		return new UsernamePasswordAuthenticationToken(username, credentials);
		
	}
	protected void setDetails(HttpServletRequest request,
			UsernamePasswordAuthenticationToken authRequest) {
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}
	
	
	
	@Override
	protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain,
			Authentication authResult) throws IOException, ServletException {
		// TODO Auto-generated method stub
		super.successfulAuthentication(request, response, chain, authResult);
	}

	public HttpOutputMessageWriter getHttpOutputMessageWriter() {
		return httpOutputMessageWriter;
	}


	public void setHttpOutputMessageWriter(
			HttpOutputMessageWriter httpOutputMessageWriter) {
		this.httpOutputMessageWriter = httpOutputMessageWriter;
	}


	protected Object createSuccessResult(HttpServletRequest request,
				HttpServletResponse response, Authentication authentication) {
		return new InvocationResult(InvocationResult.SUCCESS,null,null);
	}
	
	protected Object createFailureResult(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException exception) {
		return new InvocationResult(InvocationResult.FAILED,null,"错误的账号或密码");
	}

	
	class RestAuthenticationResultHandler implements AuthenticationSuccessHandler,AuthenticationFailureHandler {

		@Override
		public void onAuthenticationSuccess(HttpServletRequest request,
				HttpServletResponse response, Authentication authentication)
				throws IOException, ServletException {
			httpOutputMessageWriter.write(request, response, createSuccessResult(request,response,authentication));
		}

		@Override
		public void onAuthenticationFailure(HttpServletRequest request,
				HttpServletResponse response, AuthenticationException exception)
				throws IOException, ServletException {
			httpOutputMessageWriter.write(request, response, createFailureResult(request,response,exception));
		}
	}
}
